/*******************************************************************************
 * Copyright (c) 2012-2017 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.providers;

import com.google.common.io.Resources;

import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.stringtemplate.v4.ST;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * Generate implementation for {@link DynaProvider}.
 * Find all types annotated with {@link DynaObject} and add {@link com.google.inject.Provider} for them to {@link DynaProvider} implementation.
 *
 * @author Evgen Vidolob
 */
public class DynaProviderGenerator {

    private static final String TEMPLATE_PATH =
            "/".concat(DynaProviderGenerator.class.getPackage().getName().replace(".", "/")).concat("/DynaProvider.st");

    /**
     * String template instance used
     */
    private ST st;

    private final String           packageName;
    private final String           className;
    private final List<String>     classpath;
    private       List<ClassModel> dynaClasses;


    public DynaProviderGenerator(String packageName, String className, List<String> classpath) {
        this.packageName = packageName;
        this.className = className;
        this.classpath = classpath;
    }

    public String generate() throws IOException {
        dynaClasses = new ArrayList<>();
        findDynaObjects();

        ST st = getTemplate();
        st.add("packageName", packageName);
        st.add("className", className);
        st.add("classes", dynaClasses);
        return st.render();
    }

    private void findDynaObjects() throws IOException {
        ConfigurationBuilder configuration = new ConfigurationBuilder();

        List<URL> urls = new ArrayList<>();
        for (String element : classpath) {
            urls.add(new File(element).toURI().toURL());
        }

        ClassLoader contextClassLoader = URLClassLoader.newInstance(
                urls.toArray(new URL[urls.size()]),
                Thread.currentThread().getContextClassLoader());
        Thread.currentThread().setContextClassLoader(contextClassLoader);
        configuration.setUrls(ClasspathHelper.forClassLoader(contextClassLoader));
        configuration.setScanners(new SubTypesScanner(),
                                  new TypeAnnotationsScanner());
        Reflections reflection = new Reflections(configuration);

        Set<Class<?>> classes = reflection.getTypesAnnotatedWith(DynaObject.class);
        for (Class clazz : classes) {
            //accept only classes
            if (clazz.isEnum() || clazz.isInterface() || clazz.isAnnotation()) {
                continue;
            }
            dynaClasses.add(new ClassModel(clazz));
            System.out.println(String.format("New Dyna Object Found: %s", clazz.getCanonicalName()));
        }
        System.out.println(String.format("Found: %d Dyna Objects", dynaClasses.size()));
    }

    /**
     * Get the template for provider
     * @return the String Template
     */
    protected ST getTemplate() {
        if (st == null) {
            URL url = Resources.getResource(DynaProviderGenerator.class, TEMPLATE_PATH);
            try {
                st = new ST(Resources.toString(url, UTF_8));
            } catch (IOException e) {
                throw new IllegalArgumentException("Unable to read template", e);
            }
        }
        return st;
    }
}
